package v4

import (
	"fmt"
	"github.com/boltdb/bolt"
	"log"
)

type BlockChain struct {
	//数据库
	DB *bolt.DB
	//链最后一个hash
	TailHash []byte
}

const (
	blockChainDB        = "blockChain.db"
	blockChainDB_Bucket = "blockChainBucket"
	blockLastHashKey    = "blockLastHashKey"
)

func GetConst_blockChainDB() string {
	return blockChainDB
}
func GetConst_blockChainDB_Bucket() string {
	return blockChainDB_Bucket
}
func GetConst_blockLastHashKey() string {
	return blockLastHashKey
}

func (b *BlockChain) dbExits() bool {
	db, err := bolt.Open(blockChainDB, 0600, nil)
	defer db.Close() //这不能关,其他地方还要用
	if err != nil {
		log.Panic(err)
		return false
	}
	return true
}

// 创建创世区块,第一个初始块
func NewBlockChain(address string) *BlockChain {
	var tailHash []byte
	db, err := bolt.Open(blockChainDB, 0600, nil)
	defer db.Close() //这不能关,其他地方还要用
	if err != nil {
		log.Panic(err)
	}
	//写入数据
	db.Update(func(tx *bolt.Tx) error {
		//获取桶
		bucket := tx.Bucket([]byte(blockChainDB_Bucket))
		if bucket == nil {
			bucket, err := tx.CreateBucket([]byte(blockChainDB_Bucket))
			if err != nil {
				log.Panic("创建桶失败: ", err)
			}
			//创世一次就可以了
			transaction := NewCoinbaseTransaction(address, "中本聪挖出第一单")
			txs := []*Transaction{
				transaction,
			}
			newBlock := NewBlock([]byte{}, txs)
			//存入当前区块的hash: 区块字节流
			bucket.Put(newBlock.Hash, newBlock.Serialize())
			bucket.Put([]byte(blockLastHashKey), newBlock.Hash)
			//设置最后的hash到内存
			tailHash = newBlock.Hash
		} else {
			//存在桶直接拿取最后的hash值
			tailHash = bucket.Get([]byte(blockLastHashKey))
		}
		return nil
	})
	return &BlockChain{DB: db, TailHash: tailHash}
}

func GetBlockChain() *BlockChain {
	var tailHash []byte
	db, err := bolt.Open(blockChainDB, 0600, nil)
	if err != nil {
		log.Panic(err)
	}
	db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket([]byte(blockChainDB_Bucket))
		if bucket == nil {
			log.Panic("不存在的桶")
			return nil
		}
		tailHash = bucket.Get([]byte(blockLastHashKey))
		return nil
	})
	return &BlockChain{DB: db, TailHash: tailHash}
}

func (b *BlockChain) AddBlock(txs []*Transaction) {
	preHash := b.TailHash
	db := b.DB
	newBlock := NewBlock(preHash, txs)
	db.Update(func(tx *bolt.Tx) error {
		//获取桶
		bucket := tx.Bucket([]byte(blockChainDB_Bucket))
		if bucket == nil {
			log.Panic("桶不存在,不能添加区块!")
			return nil
		}
		//存入当前区块的hash: 区块字节流
		bucket.Put(newBlock.Hash, newBlock.Serialize())
		bucket.Put([]byte(blockLastHashKey), newBlock.Hash)
		//设置最后的hash到内存
		b.TailHash = newBlock.Hash
		return nil
	})
}

// 获取有效的交易
func (bc *BlockChain) FindSuitableUTXOs(address string, amount float64) (map[string][]int64, float64) {
	var spent = make(map[string][]int64)
	var it = BlockChainIterator{
		bc.DB,
		bc.TailHash,
	}
	//定义返回
	var uxtos = make(map[string][]int64)
	var totalFind float64
	for {
		block := it.Next()
		//每一个区块都有他自身的交易信息集合
		transactions := block.Transactions
		for _, transaction := range transactions {
			//遍历输出
			txOutputs := transaction.TxOutputs
		OUT:
			for j, output := range txOutputs {
				if output.ScriptPubKey == address {
					//判断是否使用了该输出
					int64s := spent[string(transaction.TXID)]
					for _, v := range int64s {
						if int64(j) == v {
							continue OUT
						}
					}
					if totalFind < amount {
						totalFind += output.Value
						//追加到map里面返回
						uxtos[string(transaction.TXID)] = append(uxtos[string(transaction.TXID)], int64(j))
						if totalFind >= amount {
							//直接返回
							return uxtos, totalFind
						}
					}
				}
			}

			//遍历输入:使用的时候[用一笔肯定会使用完全,不会存在对一笔用一半的情况],多的转入零钱(新的部分自身的输出)
			//此处的输入其实是上一笔的输出得来的
			if !transaction.IsCoinbase() {
				txInputs := transaction.TxInputs
				for _, input := range txInputs {
					//相同地址
					if input.Sig == address {
						spent[string(input.TxID)] = append(spent[string(input.TxID)], input.Index)
					}
				}
			}
		}
		if len(block.PreHash) == 0 {
			fmt.Println("迭代链上区块完成!")
			break
		}
	}
	return uxtos, totalFind
}

// 获取某地址的余额
func (bc *BlockChain) GetBalance(address string) float64 {
	var balance float64
	var spent = make(map[string][]int64)
	var it = BlockChainIterator{
		bc.DB,
		bc.TailHash,
	}
	for {
		block := it.Next()
		//每一个区块都有他自身的交易信息集合
		transactions := block.Transactions
		for _, transaction := range transactions {
			//遍历输出
			txOutputs := transaction.TxOutputs
		OUT:
			for j, output := range txOutputs {
				if output.ScriptPubKey == address {
					//判断是否使用了该输出
					int64s := spent[string(transaction.TXID)]
					for _, v := range int64s {
						if int64(j) == v {
							continue OUT
						}
					}
					//未使用就 累加余额
					balance += output.Value
				}
			}

			//遍历输入:使用的时候[用一笔肯定会使用完全,不会存在对一笔用一半的情况],多的转入零钱(新的部分自身的输出)
			//此处的输入其实是上一笔的输出得来的
			if !transaction.IsCoinbase() {
				txInputs := transaction.TxInputs
				for _, input := range txInputs {
					//相同地址
					if input.Sig == address {
						spent[string(input.TxID)] = append(spent[string(input.TxID)], input.Index)
					}
				}
			}
		}
		if len(block.PreHash) == 0 {
			fmt.Println("迭代链上区块完成!")
			break
		}
	}
	return balance
}
